package com.vinjune.bigdata.bloomfilter.redis;

import com.vinjune.bigdata.bloomfilter.BloomFilterRedis;
import com.vinjune.bigdata.bloomfilter.tool.HashUtil;

import java.nio.charset.StandardCharsets;
import java.util.List;

/**
 * Created by 000376 on 2018/3/7.
 */

public class SimpleBloomFilterRedis implements BloomFilterRedis {
    private Integer expectedElements;
    private Double falsePositiveProbability;
    private Integer size;
    private Integer hashes;
    private BloomRedisService redis;
    private HashUtil hashUtil;

    protected void complete() {
        if(this.size == null && this.expectedElements != null && this.falsePositiveProbability != null) {
            this.size = Integer.valueOf(optimalM((long)this.expectedElements.intValue(), this.falsePositiveProbability.doubleValue()));
        }

        if(this.hashes == null && this.expectedElements != null && this.size != null) {
            this.hashes = Integer.valueOf(optimalK((long)this.expectedElements.intValue(), (long)this.size.intValue()));
        }

        if(this.size != null && this.hashes != null) {
            if(this.expectedElements == null) {
                this.expectedElements = Integer.valueOf(optimalN((long)this.hashes.intValue(), (long)this.size.intValue()));
            }

            if(this.falsePositiveProbability == null) {
                this.falsePositiveProbability = Double.valueOf(optimalP((long)this.hashes.intValue(), (long)this.size.intValue(), (double)this.expectedElements.intValue()));
            }

        } else {
            throw new NullPointerException("Neither (expectedElements, falsePositiveProbability) nor (size, hashes) were specified.");
        }
    }

    protected SimpleBloomFilterRedis(int expectedElements, double falsePositiveProbability, BloomRedisService redis) {
        this.redis = redis;
        this.expectedElements = Integer.valueOf(expectedElements);
        this.falsePositiveProbability = Double.valueOf(falsePositiveProbability);
        this.complete();
        this.hashUtil = new HashUtil(this.size.intValue(), this.hashes.intValue());
    }

    public boolean add(String name, String element) {
        return this.add(name, this.toBytes(element));
    }

    public boolean add(String name, byte[] element) {
        return this.setAll(name, this.hashUtil.hash(element));
    }

    public boolean setAll(String name, int... positions) {
        List results = this.redis.transactionallyDo((p) -> {
            int[] var3 = positions;
            int var4 = positions.length;

            for(int var5 = 0; var5 < var4; ++var5) {
                int position = var3[var5];
                p.setbit(name, (long)position, true);
            }

        }, new String[0]);
        return results.stream().anyMatch((b) -> {
            return !((Boolean)b).booleanValue();
        });
    }

    public byte[] toBytes(String element) {
        return element.getBytes(StandardCharsets.UTF_8);
    }

    public static int optimalM(long n, double p) {
        return (int)Math.ceil(-1.0D * (double)n * Math.log(p) / Math.pow(Math.log(2.0D), 2.0D));
    }

    public static int optimalK(long n, long m) {
        return (int)Math.ceil(Math.log(2.0D) * (double)m / (double)n);
    }

    public static int optimalN(long k, long m) {
        return (int)Math.ceil(Math.log(2.0D) * (double)m / (double)k);
    }

    public static double optimalP(long k, long m, double insertedElements) {
        return Math.pow(1.0D - Math.exp((double)(-k) * insertedElements / (double)m), (double)k);
    }

    public Long del(String name) {
        return this.redis.del(name);
    }

    public static void main(String[] args) {
        double b = -1.0D * 1000000.0D * Math.log(0.01D) / Math.pow(Math.log(2.0D), 2.0D);
        System.out.println(b);
    }

    public boolean exactlyNotExists(String name, String element) {
        int[] positions = this.hashUtil.hash(this.toBytes(element));
        List results = this.redis.transactionallyDo((p) -> {
            int[] var3 = positions;
            int var4 = positions.length;

            for(int var5 = 0; var5 < var4; ++var5) {
                int position = var3[var5];
                p.getbit(name, (long)position);
            }

        }, new String[0]);
        return results.stream().allMatch((b) -> {
            return !b.booleanValue();
//            return false;
        });
    }

    public boolean exists(String name, String element) {
        int[] positions = this.hashUtil.hash(this.toBytes(element));
        List results = this.redis.transactionallyDo((p) -> {
            int[] var3 = positions;
            int var4 = positions.length;

            for(int var5 = 0; var5 < var4; ++var5) {
                int position = var3[var5];
                p.getbit(name, (long)position);
            }

        }, new String[0]);
        return results.stream().allMatch((b) -> {
                        return !b.booleanValue();
//            return false;
        });
    }
}
